/// Created by Nazariy Jaworski Copyright (c) 2013
/// Bot program
///
/// botserver.h -
///  implements server role mode of the bot
///
/// To Do list:
///  - run server in separate process
///  ...
///
/// v 0.1        08.04.2013

#include "bot.h"
#include "botserver.h"

RemoteBotInfo::RemoteBotInfo(QTcpSocket *remoteQTcpSocket, QObject *parent):QObject(parent)
{
    // pushing object into list
    if(parent)
    {
        ((BotServer*)parent)->BotClients.push_back(this);
        myPosition = ((BotServer*)parent)->BotClients.end();
        myPosition--;   // Pointer to [end of list - 1] = last element
    }

    this->remoteQTcpSocket = remoteQTcpSocket;
    connect(this->remoteQTcpSocket,SIGNAL(readyRead()),
        this,SLOT(OnTcpSocketInput()));
    connect(this->remoteQTcpSocket,SIGNAL(error(QAbstractSocket::SocketError)),
        this,SLOT(OnTcpSocketError(QAbstractSocket::SocketError)));
    connect(this->remoteQTcpSocket,SIGNAL(disconnected()),
        this,SLOT(OnTcpSocketDisconnected()));
}
BotServer::BotServer(QObject *parent): QObject(parent)
{
    myQTcpServer = new QTcpServer(this);
    connect(myQTcpServer,SIGNAL(newConnection()), this, SLOT(OnNewServerConnection()));
    if(!myQTcpServer->listen(QHostAddress::Any,DEFAULT_TCP_SERVER_PORT))
        Bot::GetInstance()->SLOT_FatalError("FATAL ERROR: Can't bind to TCP server socket\n");

    myQUdpServerInputSocket   = new QUdpSocket(this);
    myQUdpServerOutputSocket  = new QUdpSocket(this);
    connect(myQUdpServerInputSocket,SIGNAL(error(QAbstractSocket::SocketError)),
            this, SLOT(OnUdpServerInputSocketError(QAbstractSocket::SocketError)));
    connect(myQUdpServerOutputSocket,SIGNAL(error(QAbstractSocket::SocketError)),
            this, SLOT(OnUdpServerOutputSocketError(QAbstractSocket::SocketError)));
    connect(myQUdpServerInputSocket, SIGNAL(readyRead()), this, SLOT(OnUdpServerSocketInput()));
    if(!myQUdpServerInputSocket->bind(DEFAULT_UDP_PORT, QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint))
        Bot::GetInstance()->SLOT_FatalError("FATAL ERROR: Can't bind to UDP server input socket\n");
}

void RemoteBotInfo::OnTcpSocketInput()
{
    /// \todo
}

void RemoteBotInfo::OnTcpSocketError(QAbstractSocket::SocketError socketError)
{
    /// \todo
}

void RemoteBotInfo::OnTcpSocketDisconnected()
{
    if(parent())
        ((BotServer*)parent())->BotClients.erase(myPosition);
    Bot::GetInstance()->SIGNAL_WriteToLog(" Connection with client was lost\n");
}

QString BotServer::ShowClients()
{
    QString str;
    if(BotClients.empty())
        str = " There are no connected Bots\n";
    else
    {
        for(RemoteBotInfo *i: BotClients)
            str += i->remoteQTcpSocket->peerName() + " " +
                    i->remoteQTcpSocket->peerAddress().toString() + ":" +
                    QString::number(i->remoteQTcpSocket->peerPort()) + "\n";
    }
    return str;
}

void BotServer::OnUdpServerInputSocketError(QAbstractSocket::SocketError socketError)
{
    /// \todo
}

void BotServer::OnUdpServerOutputSocketError(QAbstractSocket::SocketError socketError)
{
    /// \todo
}

bool BotServer::isClientRegistered(const QHostAddress &address, const quint16 port)
{
    bool alreadyConnected = false;
    /// \todo Can be optimized
    for(RemoteBotInfo *i: BotClients)
        if(i->remoteQTcpSocket->peerAddress() == address &&
                i->remoteQTcpSocket->peerPort() == port)
        {
            alreadyConnected = true;
            break;
        }
    return alreadyConnected;
}

void BotServer::OnUdpServerSocketInput()
// Processing UDP input data
{
    while(myQUdpServerInputSocket->hasPendingDatagrams())
    {
        // Get message
        QByteArray datagram;
        datagram.resize(myQUdpServerInputSocket->pendingDatagramSize());
        QHostAddress sender;
        quint16 senderPort;

        myQUdpServerInputSocket->readDatagram(datagram.data(), datagram.size(),
                                &sender, &senderPort);
        // UI output and logging has already been done at client mode of the Bot

        // Process message
       if(!QString::compare(datagram.data(),MESSAGE_REQUEST))
            // Bot is Server, and recive message 'MESSAGE_REQUEST' from new Bot
        {
            // Check, is the new Bot actually new?
            if(!this->isClientRegistered(sender, senderPort))
            {
                // Sending answer 'MESSAGE_ACCEPT' to new client
                /// \todo Try somehow to send answer only to required client
                // myQUdpServerOutputSocket->writeDatagram(MESSAGE_ACCEPT, sender, DEFAULT_UDP_PORT);
                // it doesn't work for multiple sockets on same port, that's why i use broadcast
                myQUdpServerOutputSocket->writeDatagram(MESSAGE_ACCEPT, QHostAddress::Broadcast, DEFAULT_UDP_PORT);
                Bot::GetInstance()->SIGNAL_WriteToLog(
                            QString(UI_STR_MESSAGE_UDP_OUTPUT) + MESSAGE_ACCEPT + "\n");
                // Receive TCP connection
                myQTcpServer->waitForNewConnection();
                Bot::GetInstance()->SIGNAL_WriteToLog(" Bot has found new client - " + sender.toString() + ":" + QString::number(senderPort) + "\n");
            }
        }
    }
}

void BotServer::OnNewServerConnection()
{
    RemoteBotInfo *newBotClient = new RemoteBotInfo(
            myQTcpServer->nextPendingConnection(),this);
    // It automaticaly is binded to 'this'

    /// \todo add listeners, i.e. connections, i.e. bindings
    Bot::GetInstance()->SIGNAL_WriteToLog(" A new client Bot has been registered\n");
}

void BotServer::OnAcceptError(QAbstractSocket::SocketError socketError)
{
    Bot::GetInstance()->SIGNAL_WriteToLog(myQTcpServer->errorString() + "\n");
}

BotServer::~BotServer()
{
    for(RemoteBotInfo *i: BotClients)
        delete i;
    BotClients.clear();
    delete myQTcpServer;
    delete myQUdpServerInputSocket;
    delete myQUdpServerOutputSocket;
}
